package com.one.blocks.security.config;

import com.one.blocks.security.authc.password.PasswordAuthenticationProcessingFilter;
import com.one.blocks.security.authc.rememberme.RememberMeFilter;
import com.one.blocks.security.authc.smscode.SmsCodeAuthenticationProcessingFilter;
import com.one.blocks.security.config.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 * @author <a href="mailto:idler41@163.con">linfuxin</a> created on 2023-09-18 19:41:51
 */
@ConditionalOnProperty(prefix = "app.blocks.security", name = "enable", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SecurityProperties.class)
@Configuration
public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationEntryPoint unauthorizedHandler;

    @Autowired
    private LogoutHandler logoutHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private RememberMeFilter rememberMeFilter;

    @Autowired(required = false)
    private PasswordAuthenticationProcessingFilter passwordAuthenticationProcessingFilter;

    @Autowired(required = false)
    private SmsCodeAuthenticationProcessingFilter smsCodeAuthenticationProcessingFilter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .headers().frameOptions().disable().and()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 如果开启postman模拟请求会导致403后重定向到/error
                .csrf().disable()
                .httpBasic().disable()
                .rememberMe().disable()
                .formLogin().disable()
                // 基于token，所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 登出配置
                .logout().logoutUrl(securityProperties.getLogoutUrl()).addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler).and()
                .authorizeRequests()
                .antMatchers(securityProperties.getExcludeUrl().toArray(new String[]{})).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        // 认证不使用UsernamePasswordAuthenticationFilter，所以配置在LogoutFilter前面
        http.addFilterBefore(rememberMeFilter, LogoutFilter.class);
        if (passwordAuthenticationProcessingFilter != null) {
            http.addFilterBefore(passwordAuthenticationProcessingFilter, LogoutFilter.class);
        }
        if (smsCodeAuthenticationProcessingFilter != null) {
            // 手机号码认证
            http.addFilterBefore(smsCodeAuthenticationProcessingFilter, LogoutFilter.class);
        }

        // 异常处理
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
    }
}
